﻿using System;
using System.IO;
using System.Text;
using System.Collections.Generic;

using log4net;

namespace Poi.NET.util
{
    public class HexDump
    {
        public static readonly string EOL = System.Environment.NewLine;

        private static readonly char[] _hexcodes = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        private static readonly int[] _shifts = { 60, 56, 52, 48, 44, 40, 36, 32, 28, 24, 20, 16, 12, 8, 4, 0 };

        private static Object dumpLock1 = new Object();

        private HexDump()
        {
            // all static methods, so no need for a public constructor
        }

        /**
         * dump an array of bytes to an OutputStream
         *
         * @param data the byte array to be dumped
         * @param offset its offset, whatever that might mean
         * @param stream the OutputStream to which the data is to be
         *               written
         * @param index initial index into the byte array
         * @param length number of characters to output
         *
         * @exception IOException is thrown if anything goes wrong writing
         *            the data to stream
         * @exception ArrayIndexOutOfBoundsException if the index is
         *            outside the data array's bounds
         * @exception IllegalArgumentException if the output stream is
         *            null
         */
        public static void dump(byte[] data, long offset, StreamWriter stream, int index, int length)
        {
            if (data.Length == 0)
            {
                stream.WriteLine("No Data");
                stream.Flush();
                return;
            }
            if ((index < 0) || (index >= data.Length))
            {
                throw new IndexOutOfRangeException(
                    "illegal index: " + index + " into array of length "
                    + data.Length);
            }
            if (stream == null)
            {
                throw new ArgumentException("cannot write to nullstream");
            }

            long display_offset = offset + index;
            StringBuilder buffer = new StringBuilder(74);


            int data_length = Math.Min(data.Length, index + length);
            for (int j = index; j < data_length; j += 16)
            {
                int chars_read = data_length - j;

                if (chars_read > 16)
                {
                    chars_read = 16;
                }
                buffer.Append(
                            dump(display_offset)
                             ).Append(' ');
                for (int k = 0; k < 16; k++)
                {
                    if (k < chars_read)
                    {
                        buffer.Append(dump(data[k + j]));
                    }
                    else
                    {
                        buffer.Append("  ");
                    }
                    buffer.Append(' ');
                }
                for (int k = 0; k < chars_read; k++)
                {
                    if ((data[k + j] >= ' ') && (data[k + j] < 127))
                    {
                        buffer.Append((char)data[k + j]);
                    }
                    else
                    {
                        buffer.Append('.');
                    }
                }
                buffer.Append(EOL);
                stream.Write(buffer.ToString());
                stream.Flush();
                buffer.Length = 0;
                display_offset += chars_read;
            }

        }

        /**
         * dump an array of bytes to an OutputStream
         *
         * @param data the byte array to be dumped
         * @param offset its offset, whatever that might mean
         * @param stream the OutputStream to which the data is to be
         *               written
         * @param index initial index into the byte array
         *
         * @exception IOException is thrown if anything goes wrong writing
         *            the data to stream
         * @exception ArrayIndexOutOfBoundsException if the index is
         *            outside the data array's bounds
         * @exception IllegalArgumentException if the output stream is
         *            null
         */

        public static void dump(byte[] data, long offset,
                                StreamWriter stream, int index)
        {
            lock (dumpLock1)
            {
                dump(data, offset, stream, index, data.Length - index);
            }
        }

        /**
         * dump an array of bytes to a String
         *
         * @param data the byte array to be dumped
         * @param offset its offset, whatever that might mean
         * @param index initial index into the byte array
         *
         * @exception ArrayIndexOutOfBoundsException if the index is
         *            outside the data array's bounds
         * @return output string
         */

        public static String dump(byte[] data, long offset, int index)
        {
            StringBuilder buffer;
            if ((index < 0) || (index >= data.Length))
            {
                throw new IndexOutOfRangeException(
                    "illegal index: " + index + " into array of length "
                    + data.Length);
            }
            long display_offset = offset + index;
            buffer = new StringBuilder(74);

            for (int j = index; j < data.Length; j += 16)
            {
                int chars_read = data.Length - j;

                if (chars_read > 16)
                {
                    chars_read = 16;
                }
                buffer.Append(dump(display_offset)).Append(' ');
                for (int k = 0; k < 16; k++)
                {
                    if (k < chars_read)
                    {
                        buffer.Append(dump(data[k + j]));
                    }
                    else
                    {
                        buffer.Append("  ");
                    }
                    buffer.Append(' ');
                }
                for (int k = 0; k < chars_read; k++)
                {
                    if ((data[k + j] >= ' ') && (data[k + j] < 127))
                    {
                        buffer.Append((char)data[k + j]);
                    }
                    else
                    {
                        buffer.Append('.');
                    }
                }
                buffer.Append(EOL);
                display_offset += chars_read;
            }
            return buffer.ToString();
        }


        private static String dump(long value)
        {
            StringBuilder buf = new StringBuilder();
            buf.Length = 0;
            for (int j = 0; j < 8; j++)
            {
                buf.Append(_hexcodes[((int)(value >> _shifts[j + _shifts.Length - 8])) & 15]);
            }
            return buf.ToString();
        }

        private static String dump(byte value)
        {
            StringBuilder buf = new StringBuilder();
            buf.Length = 0;
            for (int j = 0; j < 2; j++)
            {
                buf.Append(_hexcodes[(value >> _shifts[j + 6]) & 15]);
            }
            return buf.ToString();
        }

        /**
         * Converts the parameter to a hex value.
         *
         * @param value     The value to convert
         * @return          A String representing the array of bytes
         */
        public static String toHex(byte[] value)
        {
            StringBuilder retVal = new StringBuilder();
            retVal.Append('[');
            for (int x = 0; x < value.Length; x++)
            {
                if (x > 0)
                {
                    retVal.Append(", ");
                }
                retVal.Append(toHex(value[x]));
            }
            retVal.Append(']');
            return retVal.ToString();
        }

        /**
         * Converts the parameter to a hex value.
         *
         * @param value     The value to convert
         * @return          A String representing the array of shorts
         */
        public static String toHex(short[] value)
        {
            StringBuilder retVal = new StringBuilder();
            retVal.Append('[');
            for (int x = 0; x < value.Length; x++)
            {
                retVal.Append(toHex(value[x]));
                retVal.Append(", ");
            }
            retVal.Append(']');
            return retVal.ToString();
        }

        /**
         * <p>Converts the parameter to a hex value breaking the results into
         * lines.</p>
         *
         * @param value        The value to convert
         * @param bytesPerLine The maximum number of bytes per line. The next byte
         *                     will be written to a new line
         * @return             A String representing the array of bytes
         */
        public static String toHex(byte[] value, int bytesPerLine)
        {
            int digits =
                (int)Math.Round(Math.Log(value.Length) / Math.Log(10) + 0.5);
            StringBuilder formatString = new StringBuilder();
            for (int i = 0; i < digits; i++)
                formatString.Append('0');
            formatString.Append(": ");
            //DecimalFormat format = new DecimalFormat(formatString.ToString());
            StringBuilder retVal = new StringBuilder();
            retVal.Append(((Int32)0).ToString(formatString.ToString()));
            int j = -1;
            for (int x = 0; x < value.Length; x++)
            {
                if (++j == bytesPerLine)
                {
                    retVal.Append('\n');
                    retVal.Append(x.ToString(formatString.ToString()));
                    j = 0;
                }
                retVal.Append(toHex(value[x]));
                retVal.Append(", ");
            }
            return retVal.ToString();
        }

        /**
         * Converts the parameter to a hex value.
         *
         * @param value     The value to convert
         * @return          The result right padded with 0
         */
        public static String toHex(short value)
        {
            return toHex(value, 4);
        }

        /**
         * Converts the parameter to a hex value.
         *
         * @param value     The value to convert
         * @return          The result right padded with 0
         */
        public static String toHex(byte value)
        {
            return toHex(value, 2);
        }

        /**
         * Converts the parameter to a hex value.
         *
         * @param value     The value to convert
         * @return          The result right padded with 0
         */
        public static String toHex(int value)
        {
            return toHex(value, 8);
        }

        /**
         * Converts the parameter to a hex value.
         *
         * @param value     The value to convert
         * @return          The result right padded with 0
         */
        public static String toHex(long value)
        {
            return toHex(value, 16);
        }


        private static String toHex(long value, int digits)
        {
            StringBuilder result = new StringBuilder(digits);
            for (int j = 0; j < digits; j++)
            {
                result.Append(_hexcodes[(int)((value >> _shifts[j + (16 - digits)]) & 15)]);
            }
            return result.ToString();
        }

        /**
         * Dumps <code>bytesToDump</code> bytes to an output stream.
         *
         * @param in          The stream to read from
         * @param out         The output stream
         * @param start       The index to use as the starting position for the left hand side label
         * @param bytesToDump The number of bytes to output.  Use -1 to read until the end of file.
         */
        public static void dump(StreamReader ins, StreamWriter outs, int start, int bytesToDump)
        {
            MemoryStream buf = new MemoryStream();
            if (bytesToDump == -1)
            {
                int c = ins.Read();
                while (c != -1)
                {
                    buf.WriteByte((byte)c);
                    c = ins.Read();
                }
            }
            else
            {
                int bytesRemaining = bytesToDump;
                while (bytesRemaining-- > 0)
                {
                    int c = ins.Read();
                    if (c == -1)
                    {
                        break;
                    }
                    buf.WriteByte((byte)c);
                }
            }

            byte[] data = buf.ToArray();
            dump(data, 0, outs, start, data.Length);
        }

        /**
         * @return char array of uppercase hex chars, zero padded and prefixed with '0x'
         */
        private static char[] toHexChars(ulong pValue, int nBytes)
        {
            int charPos = 2 + nBytes * 2;
            // The return type is char array because most callers will probably append the value to a
            // StringBuffer, or write it to a Stream / Writer so there is no need to create a String;
            char[] result = new char[charPos];

            ulong value = pValue;
            do
            {
                result[--charPos] = _hexcodes[(int)(value & 0x0F)];
                value >>= 4;
            } while (charPos > 1);

            // Prefix added to avoid ambiguity
            result[0] = '0';
            result[1] = 'x';
            return result;
        }
        /**
         * @return char array of 4 (zero padded) uppercase hex chars and prefixed with '0x'
         */
        public static char[] longToHex(ulong value)
        {
            return toHexChars((ulong)value, 8);
        }
        /**
         * @return char array of 4 (zero padded) uppercase hex chars and prefixed with '0x'
         */
        public static char[] intToHex(int value)
        {
            return toHexChars((ulong)value, 4);
        }
        /**
         * @return char array of 2 (zero padded) uppercase hex chars and prefixed with '0x'
         */
        public static char[] shortToHex(int value)
        {
            return toHexChars((ulong)value, 2);
        }
        /**
         * @return char array of 1 (zero padded) uppercase hex chars and prefixed with '0x'
         */
        public static char[] byteToHex(int value)
        {
            return toHexChars((ulong)value, 1);
        }

        /*
        public static void main(String[] args) {
            File file = new File(args[0]);
            InputStream in = new BufferedInputStream(new FileInputStream(file));
            byte[] b = new byte[(int)file.length()];
            in.read(b);
            System.out.println(HexDump.dump(b, 0, 0));
            in.close();
        }
         */
    }
}
